'''
Created on Apr 17, 2011

@author: Jonathan Herbst
'''

import uuid, subprocess, re, os, time, socket
from threading import Thread, Event

# bluez functions and classes
from bluetooth import advertise_service, stop_advertising, BluetoothSocket
# bluez constants
from bluetooth import RFCOMM, PORT_ANY, SERIAL_PORT_CLASS, SERIAL_PORT_PROFILE

from Utilities import LogFile
import Messages

class BluetoothInitException(Exception): pass

class Bluetooth(Thread):
    
    _bufferSize = 1024
    
    _acceptableMessageClasses = [Messages.DoorCommand]
    
    _btInterfaceInfoCommand = ["hciconfig"]
    _btInterfaceDiscoverableCommand = ["hciconfig", "hci0", "piscan"]
    
    _discoverableRE = re.compile("hci0:\s+Type:\s+UART\s+BD\s+Address:\s+[\dA-Fa-f:]+\s+ACL\s+MTU:\s+[\d:]+\s+SCO\s+MTU:\s+[\d:]+\s+UP\s+RUNNING\s+PSCAN\s+ISCAN")
    
    def __init__(self, config, messageQueue):
        self.t_exit = False
        self.m_responses = []
        self.t_respondEvent = Event()
        self.m_uuid = config.bluetoothUUID
        self.m_messageQueue = messageQueue
        # if we don't have a uuid, generate one
        if not self.m_uuid:
            self.m_uuid = str(uuid.uuid4())
            config.bluetoothUUID = self.m_uuid
            
        self.m_log = LogFile("bluetooth")
        
        self.m_log.Write("Init", "Starting Bluetooth")
        
        self.m_log.Write("Init", "Initializing Bluetooth Hardware", LogFile.DEBUG)
        #if not self._InitBluetoothHardware():
        #    self.m_log.Write("Init", "Bluetooth Hardware Initialization FAIL", LogFile.FATAL)
        #    raise BluetoothInitException()
        self.m_log.Write("Init", "Bluetooth Hardware Initialization SUCCESS", LogFile.DEBUG)
        
        self.m_log.Write("Init", "Making socket", LogFile.DEBUG)
        self.m_socket = BluetoothSocket(RFCOMM)
        self.m_log.Write("Init", "Socket made", LogFile.DEBUG)
            
        self.m_log.Write("Init", "Starting thread", LogFile.DEBUG)
        Thread.__init__(self)
        self.daemon = True
        self.start()
        self.m_log.Write("Init", "Thread started", LogFile.DEBUG)
        
    def _InitBluetoothHardware(self):
        
        def _CheckDiscoverable():
            try:
                p = subprocess.Popen(Bluetooth._btInterfaceInfoCommand, stdout=subprocess.PIPE)
            except:
                return None
            out, err = p.communicate()
            if err is not None:
                return None
            
            if Bluetooth._discoverableRE.search(out) is not None:
                return True
            return False
        
        def _MakeDiscoverable():
            try:
                p = subprocess.Popen(Bluetooth._btInterfaceDiscoverableCommand)
            except:
                return False
            os.waitpid(p.pid, 0)
            return True
        
        discoverable = _CheckDiscoverable()
        while discoverable is False:
            time.sleep(1)
            if not _MakeDiscoverable():
                return False
            discoverable = _CheckDiscoverable()
        if discoverable is None:
            # the command failed
            return False
        return True
        
    def Respond(self, messages):
        for message in messages:
            self.m_responses += message.renderMessage()
        self.t_respondEvent.set()
        
    def Stop(self):
        self.t_exit = True
        stop_advertising(self.m_socket)
        self.m_socket.close()
        
    def run(self):
        # bind the socket
        self.m_socket.bind(("", PORT_ANY))
        self.m_socket.listen(1)
        
        #advertise the socket
        advertise_service(self.m_socket, "SECSControlDevice",
                          service_id = self.m_uuid,
                          service_classes = [ self.m_uuid, SERIAL_PORT_CLASS ],
                          profiles = [ SERIAL_PORT_PROFILE ])

        while not self.t_exit:
            try:
                # accept a connection
                clientSock, clientInfo = self.m_socket.accept()
                
                print "recieving"
                # get data from the connection
                data = clientSock.recv(self._bufferSize)
                while len(data) >= self._bufferSize and not self.t_exit:
                    data += clientSock.recv(self._bufferSize)
                print "here"
                
                # handle the data
                if not self.t_exit:
                    responses = self._HandleData(data)
                    for response in responses:
                        clientSock.send(str(response))
            except:
                # just try again if we get an exception
                pass
                    
            # close the connection
            clientSock.close()
            
        # stop advertising the socket
        stop_advertising(self.m_socket)
        
        # close the socket
        self.m_socket.close()
            
    def _HandleData(self, data):
        data = str(data).strip()
        
        for message in data.split(';'):
            message = Messages.parseMessage(str(message) + ';')
            if message is not None:
                if message.__class__ in self._acceptableMessageClasses:
                    if self._ValidateMessage(message):
                        if message.__class__ == Messages.DoorCommand:
                            self.m_messageQueue.Push(Messages.BTDoorCommand(message))
        return []
        
    def _ValidateMessage(self, message):
        return True
